﻿/**
*	Physics
*	@author 	Liam O'Donnell 2008-04-01
**/

package com.spikything.pants3d.core {
	
	import com.skivecreative.canonrebound.events.GameEvent;
	import com.skivecreative.canonrebound.objects.Ball;
	import com.spikything.pants3d.events.PhysicsEvent;
	import com.spikything.pants3d.core.Point3D;
	import com.spikything.pants3d.objects.IRenderable;
	import com.spikything.pants3d.objects.Sprite3D;
	import com.spikything.pants3d.objects.PhysicsSprite3D;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	public class Physics {

		public static var inst 			:Physics;
		
		public var friction 			:Number 	= .96; 	// Air/floor friction
		public var gravity 				:Number 	= 3.8; 	// Global gravity
		public var elasticity 			:Number 	= -.62; // Coefficient of restitution
		
		public var floor 				:Number 	= 0;
		public var left 				:Number 	= -700;
		public var right 				:Number 	= 700;
		public var back 				:Number 	= 650;
		public var front 				:Number 	= -400;
		//public var walls 				:Array;
		//public var obstacles 			:Array;
		
		private var beacon 				:EventDispatcher;

		public function Physics (beacon:EventDispatcher) {
			inst = this;
			this.beacon = beacon;
			//walls = new Array();
			//obstacles = new Array();
		}
		
		public function getInstance ():Physics {
			if (!inst) inst = new Physics(null);
			return inst;
		}
		
		// TODO Physics to contain reference to Projector?
		/*
		public function addWall (x:Number, y:Number, z:Number, plane:String, polarity:int):void {
			var wall:Object = { x:x, y:y, z:z, plane:plane, polarity:polarity };
			walls.push(wall);
		}

		public function addObstacle (obstacle:IRenderable):void {
			obstacles.push(obstacle);
		}
		*/
		
		public function updateObjects (sprites:Array):void {
			for (var i:int=0; i<sprites.length; i++) updateObject(sprites[i], sprites);
		}
		
		public function updateObject (sprite:IRenderable, sprites:Array):void {
			if (sprite.toString()=="PhysicsSprite3D") {
				(sprite as PhysicsSprite3D).applyForces(gravity);
				(sprite as PhysicsSprite3D).applyFriction(friction);
			}
			sprite.update();
			//if (sprite.toString()=="PhysicsSprite3D") (sprite as PhysicsSprite3D).checkCollisions(this);
			// TODO move this out after updateObject (so position, then check, then reposition)
			if (sprite.toString()=="PhysicsSprite3D") checkCollisions((sprite as PhysicsSprite3D), sprites);
			//if (sprite.id=="PhysicsSprite3D") checkCollisions((sprite as PhysicsSprite3D), sprites);
		}
		
		protected function checkCollisions (sprite:IRenderable, sprites:Array):void {
			containSprite(sprite as PhysicsSprite3D);
			if (!sprite.isCollidable) return;
			for (var i:int = 0; i < sprites.length; i++) {
				if (sprite != sprites[i] && sprites[i].toString()=="PhysicsSprite3D") if (checkCollision(sprite, sprites[i])) handleCollision(sprite, sprites[i]);
			}
		}
		
		protected function checkCollision (sprite1:IRenderable, sprite2:PhysicsSprite3D):Boolean {
			// TODO account for sprite1 radius in collision detection (radii<separation)?
			if (sprite2.containsPoint(new Point3D(sprite1.px, sprite1.py, sprite1.pz))) return true;
			return false;
		}

		protected function handleCollision (sprite1:IRenderable, sprite2:IRenderable):void {
			var inertia:Number = 6;
			(sprite2 as Sprite3D).impulse((sprite1 as Sprite3D).vx/inertia, (sprite1 as Sprite3D).vy/inertia, (sprite1 as Sprite3D).vz/inertia);
			sprite1.pz -= (sprite1 as Sprite3D).vz;
			(sprite1 as Sprite3D).vz *= elasticity;
			(sprite1 as Sprite3D).dispatchEvent(new PhysicsEvent(PhysicsEvent.COLLISION, sprite2));
			(sprite2 as Sprite3D).dispatchEvent(new PhysicsEvent(PhysicsEvent.COLLISION, sprite1));
		}
		
		protected function containSprite (sprite:PhysicsSprite3D):void {
			if (sprite.ghostMode) return;
			// floor
			if (sprite.py >= floor-sprite.h/2) {
				sprite.py = floor-sprite.h/2;
				sprite.vy *= elasticity;
				sprite.applyFriction(friction);
				// TODO clean this bounce check up
				if (sprite.id == "Ball") sprite.dispatchEvent(new GameEvent(GameEvent.BOUNCE, (sprite as Ball).speed));
			}
			
			if (!sprite.isContained) return;
			
			// backwall
			// TODO adjust these for r/2?
			// TODO limit wall width by left and right params?
			if (sprite.pz >= back) { // && sprite.px <= right && sprite.px >= left
				sprite.pz = back;
				sprite.vz *= elasticity;
				sprite.applyFriction(friction);
				beacon.dispatchEvent(new PhysicsEvent(PhysicsEvent.HIT_WALL, sprite));
			} else if (sprite.pz <= front) {
				sprite.pz = front;
				sprite.vz *= elasticity;
				sprite.applyFriction(friction);
			}
			// right wall
			if (sprite.px >= right) {
				sprite.px = right;
				sprite.vx *= elasticity;
				sprite.applyFriction(friction);
			// left wall
			} else if (sprite.px <= left) {
				sprite.px = left;
				sprite.vx *= elasticity;
				sprite.applyFriction(friction);
			}
		}
		
	}
	
}